package iblink.adapter;

import java.util.Date;

import packutil.DateUtil;
import packutil.ThreadUtil;
import packutil.throttle.IThrottle;

import iblink.core.ISecurity;
import iblink.core.ScannerArgs;
import iblink.util.Constants.BarSize;
import iblink.util.Constants.DataType;
import iblink.util.Constants.Duration;

import com.ib.client.Contract;
import com.ib.client.EClientSocket;

public class IBClient {

	private static final String IB_DATE_FORMAT = "yyyyMMdd HH:mm:ss";

	private IThrottle historicalDataThrottle;

	private final EClientSocket ibSocket;
	private final int clientId;
	private final String host;
	private final int port;

	private boolean twsConnectivity = false;
	private boolean ibConnectivity = true;
	private boolean histDataConnectivity = true;

	IBClient(final IBWrapper wrapper, IThrottle historicalDataThrottle,
			final int clientId, final String host, final int port) {
		this.ibSocket = new EClientSocket(wrapper);
		this.historicalDataThrottle = historicalDataThrottle;
		this.clientId = clientId;
		this.host = host;
		this.port = port;
		this.wrapper().setClient(this);
	}

	protected void couldNotConnectToTWS() {
		twsConnectivity = false;
	}

	protected void ibConnectivityLost() {
		ibConnectivity = false;
	}

	protected void ibConnectivityBroken() {
		ibConnectivity = false;
	}

	protected void ibConnectivityRestoredDataLost() {
		ibConnectivity = true;
	}

	protected void ibConnectivityRestoredDataMaintained() {
		ibConnectivity = true;
	}

	protected void twsConnectionSocketDropped() {
		twsConnectivity = false;
	}

	protected void historicalDataFarmConnected() {
		histDataConnectivity = true;
	}

	protected void historicalDataFarmDisconnected() {
		histDataConnectivity = false;
	}

	public synchronized void connect() {

		if (!ibSocket.isConnected()) {

			// TODO: if i want to leave this retry in place, neaten up the code.
			// TODO: eg, last isConnected check is redundant

			// one automatic retry for connection failure..
			for (int i = 0; i < 2; i++) {

				ibSocket.eConnect(host, port, clientId);

				if (ibSocket.isConnected()) {
					twsConnectivity = true;
					return;
				}

				ThreadUtil.sleep(1000);
			}

			throw new IllegalStateException("Could not connect to IB.");

		} else {
			// TODO: log warning
			System.out.println("WARNING: Already connected.. why am I here..");
			twsConnectivity = true;
		}
	}

	public void checkConnection() {

		if (!ibSocket.isConnected() || !twsConnectivity)
			connect();

		for (int t = 0; t < 2; t++) {

			// wait for hist data connection
			for (int i = 0; i < 20; i++) {
				if (ibConnectivity)
					break;
				if (i == 19)
					throw new RuntimeException("no ib connectivity");
				System.out.println("no ib connectivity, sleeping");
				ThreadUtil.sleep(1000);

				tryReconnect();
			}
		}
	}

	public void tryReconnect() {
		disconnect();
		this.ibConnectivity = true;
		this.histDataConnectivity = true;
		ThreadUtil.sleep(1000);
		connect();
	}

	public boolean isConnected() {
		return ibSocket.isConnected();
	}

	public synchronized void disconnect() {
		ibSocket.eDisconnect();
	}

	public int getClientId() {
		return clientId;
	}

	public IBWrapper wrapper() {

		if (ibSocket.wrapper() instanceof IBWrapper) {
			return (IBWrapper) ibSocket.wrapper();
		}

		throw new IllegalStateException("IBClient only works with IBWrapper");
	}

	public void reqHistoricalData(final int requestId, final ISecurity sec,
			final Date endDateTime, final Duration duration,
			final BarSize barSize, final DataType whatToShow,
			final boolean useRegularTradingHours) {

		historicalDataThrottle.touch();

		Contract contract = IBHelper.getContract(sec);

		String endDateTimeStr = DateUtil
				.formatDate(endDateTime, IB_DATE_FORMAT);

		String durationStr = duration.getIbString();
		String barSizeSetting = barSize.getIbString();
		String whatToShowStr = whatToShow.getIbString();
		int useRTH = useRegularTradingHours ? 1 : 0;
		int formatDate = 1;

		// wait for hist data connection
		for (int i = 0; i < 20; i++) {
			if (histDataConnectivity)
				break;
			if (i == 19)
				throw new RuntimeException("no hist data connectivity");
			System.out.println("no hist data connectivity, sleeping");
			ThreadUtil.sleep(1000);
		}

		checkConnection();

		ibSocket.reqHistoricalData(requestId, contract, endDateTimeStr,
				durationStr, barSizeSetting, whatToShowStr, useRTH, formatDate);
	}

	public void reqContractDetails(final int reqId, final Contract contract) {
		checkConnection();
		ibSocket.reqContractDetails(reqId, contract);
	}

	public void reqScannerParameters() {
		checkConnection();
		ibSocket.reqScannerParameters();
	}

	public void reqScannerSubscription(final int tickerId,
			final ScannerArgs scannerArgs) {
		checkConnection();
		ibSocket.reqScannerSubscription(tickerId,
				scannerArgs.asScannerSubscription());
	}

	public void cancelScannerSubscription(final int tickerId) {
		checkConnection();
		ibSocket.cancelScannerSubscription(tickerId);
	}

	/*
	 * @Override public void calculateImpliedVolatility(final int reqId, final
	 * Contract contract, final double optionPrice, final double underPrice) {
	 * checkConnection(); super.calculateImpliedVolatility(reqId, contract,
	 * optionPrice, underPrice); }
	 * 
	 * @Override public void calculateOptionPrice(final int reqId, final
	 * Contract contract, final double volatility, final double underPrice) {
	 * checkConnection(); super.calculateOptionPrice(reqId, contract,
	 * volatility, underPrice); }
	 * 
	 * @Override public void cancelCalculateImpliedVolatility(final int reqId) {
	 * checkConnection(); super.cancelCalculateImpliedVolatility(reqId); }
	 * 
	 * @Override public void cancelCalculateOptionPrice(final int reqId) {
	 * checkConnection(); super.cancelCalculateOptionPrice(reqId); }
	 * 
	 * @Override public void cancelFundamentalData(final int reqId) {
	 * checkConnection(); super.cancelFundamentalData(reqId); }
	 * 
	 * public void cancelHistoricalData(final int tickerId) { checkConnection();
	 * super.cancelHistoricalData(tickerId); }
	 * 
	 * public void cancelMktData(final int tickerId) { checkConnection();
	 * super.cancelMktData(tickerId); }
	 * 
	 * public void cancelMktDepth(final int tickerId) { checkConnection();
	 * super.cancelMktDepth(tickerId); }
	 * 
	 * public void cancelNewsBulletins() { checkConnection();
	 * super.cancelNewsBulletins(); }
	 * 
	 * public void cancelOrder(final int id) { checkConnection();
	 * super.cancelOrder(id); }
	 * 
	 * public void cancelRealTimeBars(final int tickerId) { checkConnection();
	 * super.cancelRealTimeBars(tickerId); }
	 * 
	 * public void exerciseOptions(final int tickerId, final Contract contract,
	 * final int exerciseAction, final int exerciseQuantity, final String
	 * account, final int override) { checkConnection();
	 * super.exerciseOptions(tickerId, contract, exerciseAction,
	 * exerciseQuantity, account, override); }
	 * 
	 * public void placeOrder(final int id, final Contract contract, final Order
	 * order) { checkConnection(); super.placeOrder(id, contract, order); }
	 * 
	 * public void replaceFA(final int faDataType, final String xml) {
	 * checkConnection(); super.replaceFA(faDataType, xml); }
	 * 
	 * public void reqAccountUpdates(final boolean subscribe, final String
	 * acctCode) { checkConnection(); super.reqAccountUpdates(subscribe,
	 * acctCode); }
	 * 
	 * public void reqAllOpenOrders() { checkConnection();
	 * super.reqAllOpenOrders(); }
	 * 
	 * public void reqAutoOpenOrders(final boolean bAutoBind) {
	 * checkConnection(); super.reqAutoOpenOrders(bAutoBind); }
	 * 
	 * public void reqContractDetails(final int reqId, final Contract contract)
	 * { checkConnection(); super.reqContractDetails(reqId, contract); }
	 * 
	 * public void reqCurrentTime() { checkConnection(); super.reqCurrentTime();
	 * }
	 * 
	 * public void reqExecutions(final int reqId, final ExecutionFilter filter)
	 * { checkConnection(); super.reqExecutions(reqId, filter); }
	 * 
	 * public void reqFundamentalData(final int reqId, final Contract contract,
	 * final String reportType) { checkConnection();
	 * super.reqFundamentalData(reqId, contract, reportType); } public void
	 * reqIds(final int numIds) { checkConnection(); super.reqIds(numIds); }
	 * 
	 * public void reqManagedAccts() { checkConnection();
	 * super.reqManagedAccts(); }
	 * 
	 * public void reqMktData(final int tickerId, final Contract contract, final
	 * String genericTickList, final boolean snapshot) { checkConnection();
	 * super.reqMktData(tickerId, contract, genericTickList, snapshot); }
	 * 
	 * public void reqMktDepth(final int tickerId, final Contract contract,
	 * final int numRows) { checkConnection(); super.reqMktDepth(tickerId,
	 * contract, numRows); }
	 * 
	 * public void reqNewsBulletins(final boolean allMsgs) { checkConnection();
	 * super.reqNewsBulletins(allMsgs); }
	 * 
	 * public void reqOpenOrders() { checkConnection(); super.reqOpenOrders(); }
	 * 
	 * public void reqRealTimeBars(final int tickerId, final Contract contract,
	 * final int barSize, final String whatToShow, final boolean useRTH) {
	 * checkConnection(); super.reqRealTimeBars(tickerId, contract, barSize,
	 * whatToShow, useRTH); }
	 * 
	 * 
	 * 
	 * public void requestFA(final int faDataType) { checkConnection();
	 * super.requestFA(faDataType); }
	 */

}